package iso8583

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"strconv"
)

type parserDriver struct {
	Result
	*bufio.Reader
	status    int
	templates *template
}

type IsoError struct {
	Status      string
	Description string
}

func (err *IsoError) Error() string {
	return "iso8583:" + err.Status + ":" + err.Description
}

func (iso *Iso) Unmarshal(io io.Reader) (Result, error) {
	driver := &parserDriver{
		make(Result),
		bufio.NewReader(io),
		PARSE_START,
		&iso.template}
	err := driver.Do()
	if err != nil {
		return nil, err
	}
	return driver.Result, nil
}

func (driver *parserDriver) Do() error {
	for {
		switch driver.status {
		case PARSE_START:
			driver.status = PARSE_MSG
		case PARSE_MSG:
			err := driver.parserMsg()
			if err != nil {
				return err
			}
			driver.status = PARSE_BITMAP
		case PARSE_BITMAP:
			ok, err := driver.parserBitmap()
			if err != nil {
				return err
			}
			if ok {
				driver.status = PARSE_FIELD
			}
		case PARSE_FIELD:
			err := driver.parserAllFields()
			if err != nil {
				return err
			}
			driver.status = PARSE_END
		case PARSE_END:
			return nil
		default:
			return nil
		}

	}
}

func (driver *parserDriver) parserMsg() error {
	b, err := driver.ReadByte()
	if err != nil {
		return &IsoError{"PARSE_MSG", err.Error()}
	}
	b2, err := driver.ReadByte()
	if err != nil {
		return &IsoError{"PARSE_MSG", err.Error()}
	}

	driver.Result[MSG] = append(driver.Result[MSG], b, b2)

	return nil
}

func (driver *parserDriver) parserBitmap() (bool, error) {
	b, err := driver.ReadByte()
	if err != nil {
		return false, &IsoError{"PARSE_BITMAP", err.Error()}
	}
	driver.Result[BITMAP] = append(driver.Result[BITMAP], b)
	if len(driver.Result[BITMAP]) != 8 {
		return false, nil
	}
	bitmapString := ""
	for i := 0; i < 8; i++ {
		bitmapString += fmt.Sprintf("%08b", driver.Result[BITMAP][i])
	}
	driver.Result[BITMAP] = []byte(bitmapString)
	return true, nil
}

func (driver *parserDriver) parserAllFields() error {
	bitmap := driver.Result[BITMAP]

	for i := 0; i < 64; i++ {
		err := driver.parserField(i+1, bitmap[i])
		if err != nil {
			return err
		}
	}
	return nil
}

func (driver *parserDriver) parserField(num int, isset byte) error {
	if num == 1 && isset == '1' {
		return &IsoError{"PARSE_FIELD[1]", "could not support 128 field"}
	}

	if isset == '0' {
		return nil
	}

	this := driver.templates[num-1]

	if this.num == FIELD_NOT_USE {
		return nil
	}

	var (
		length int
		err    error
	)

	switch this.lenSection._type {
	case FIX:
		length = this.dataSection.maxLen
	case LLVAR:

		length, err = driver.parserLenSectionLLVAR(this.lenSection.encode)
		if err != nil {
			return err
		}
	case LLLVAR:
		length, err = driver.parserLenSectionLLLVAR(this.lenSection.encode)
		if err != nil {
			return err
		}
	}
	if length > this.dataSection.maxLen {
		return &IsoError{"PARSE_FIELD", fmt.Sprintf("[%d] {length:%d} > {maxlen:%d}", this.num, length, this.dataSection.maxLen)}
	}
	err = driver.parserDataSection(&this, length)
	if err != nil {
		return err
	}

	return nil

}

func (driver *parserDriver) parserDataSection(this *field, length int) error {

	var readbytes int

	switch this.dataSection.encode {
	case BCD_LEFT:
		fallthrough
	case BCD_RIGHT:
		readbytes = int(math.Ceil(float64(length) / 2))
	case BIN:
		fallthrough
	case ASCII:
		readbytes = length
	default:
		return &IsoError{"PARSE_FIELD", fmt.Sprintf("[%d] unkonw {data:%s}", this.num, this.dataSection.encode)}
	}

	data := make([]byte, 0, 2048)

	for len(data) < readbytes {
		b, err := driver.ReadByte()
		if err != nil {
			return &IsoError{"PARSE_FIELD", fmt.Sprintf("[%d] {error:%s ---the data len was set %d, but only read %d}:", this.num, err.Error(), readbytes, len(data))}
		}
		data = append(data, b)
	}

	var out []byte
	switch this.dataSection.encode {
	case BCD_LEFT:
		//12390
		out = []byte(fmt.Sprintf("%02x", data))
		out = out[:length]
	case BCD_RIGHT:
		//01239
		out = []byte(fmt.Sprintf("%02x", data))
		out = out[1:]
	case BIN:
		fallthrough
	case ASCII:
		out = data
	}
	driver.Result[this.num] = out

	return nil

}

func (driver *parserDriver) parserLenSectionLLVAR(encode string) (int, error) {
	var length int

	switch encode {
	case BCD_LEFT:
		fallthrough
	case BCD_RIGHT:
		b, err := driver.ReadByte()
		if err != nil {
			return -1, err
		}
		length, _ = strconv.Atoi(fmt.Sprintf("%02x", b))
	case ASCII:
		fallthrough
	case HEX:
		fallthrough
	case BIN:
		var b2 [2]byte
		var err error
		b2[0], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		b2[1], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}

		if encode == ASCII {
			length, err = strconv.Atoi(fmt.Sprintf("%s", b2))
			if err != nil {
				return -1, err
			}
		} else {
			length = int(b2[0])*256 + int(b2[1])
		}
	default:
		return -1, &IsoError{"PARSE_FIELD", "unkown len section type"}
	}
	return length, nil
}

func (driver *parserDriver) parserLenSectionLLLVAR(encode string) (int, error) {
	var length int

	switch encode {
	case BCD_LEFT:
		fallthrough
	case BCD_RIGHT:
		var b2 [2]byte
		var err error
		b2[0], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		b2[1], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		length, _ = strconv.Atoi(fmt.Sprintf("%02x", b2))
	case ASCII:
		var b3 [3]byte
		var err error
		b3[0], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		b3[1], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		b3[2], err = driver.ReadByte()
		if err != nil {
			return -1, err
		}
		if encode == ASCII {
			length, err = strconv.Atoi(fmt.Sprintf("%s", b3))
			if err != nil {
				return -1, err
			}
		} else {
			length = int(b3[0])*256 + int(b3[1])
		}
	default:
		return -1, &IsoError{"PARSE_FIELD", "unkown len section type"}
	}
	return length, nil
}
